package com.excel.validators.core.extension;

import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;

import com.excel.validators.core.decorative.BasicClass;
import com.excel.validators.core.extension.annotation.AuditMetaData;
import com.excel.validators.core.extension.annotation.BaseNode;
import com.excel.validators.core.extension.annotation.DateNode;
import com.excel.validators.core.extension.annotation.DecimalNode;
import com.excel.validators.core.extension.annotation.ExcelNode;
import com.excel.validators.core.extension.annotation.FieldNode;
import com.excel.validators.core.extension.annotation.IngoreNode;
import com.excel.validators.core.extension.annotation.InnerModel;
import com.excel.validators.core.extension.annotation.InnerSummary;
import com.excel.validators.core.extension.annotation.NoneNode;
import com.excel.validators.core.extension.annotation.Validated;

/**
 * @author wangxiaoliang 10
 */
public class FactoryAnnotated {

  private Map<String,InnerModel> process = new LinkedHashMap<String,InnerModel>();
  private Map<String,String> validators = new LinkedHashMap<String,String>();
  private Elements elementUtils;
  private int banner = -1;

	public FactoryAnnotated(Elements elementUtils) throws ProcessingException {
	  	this.elementUtils = elementUtils;
	}
	
  /**
   * analysis for model classes and get field elements
   * @param typeElement
   * @throws ClassNotFoundException 
   */
  public void addModelElement(TypeElement typeElement) {
	  	// get annotation of @excelNode
	  	List<Annotation> fields = new LinkedList<Annotation>();
	  	
	  	ExcelNode excelNode = typeElement.getAnnotation(ExcelNode.class);
	  	List<? extends Element> members = elementUtils.getAllMembers(typeElement);

	  	for (Element element : members) {
	  			if (element.getKind() == ElementKind.FIELD) {
	  					VariableElement field = (VariableElement) element;
	  					// field type 
	  					String clsType = field.asType().toString();
	  					// check is type our required,that it will be processed 
	  					int ctype = BasicClass.enumType(clsType);
	  					if (ctype != BasicClass.UNNONE) {
			  					// field annotation
	  							IngoreNode ingoreNode = field.getAnnotation(IngoreNode.class);
	  							// if field be ignored , skip this field
	  							if (ingoreNode != null) continue;
	  							// if field has annotation with @FieldNode
	  							FieldNode	fieldNode = createFieldNode(field);
	  							BaseNode baseNode = createBaseNode(ctype,field);
	  							AuditMetaData metadata = createAuditMetaData(field,fieldNode,baseNode);
	  							fields.add(metadata);
	  					}
	  			}
	  	}
	  	InnerModel excelModel = createFields(typeElement,excelNode,fields);
	  	process.put(excelNode.namespace(), excelModel);
	  	resetBanner();
  }

  private void resetBanner() {
	  	banner = -1;
  }

private InnerModel createFields(final TypeElement typeElement, final ExcelNode excelNode, final List<Annotation> fields) {
	  	return new InnerModel() {
					@Override
					public Class<? extends Annotation> annotationType() {
							return null;
					}
					@Override
					public String namespace() {
							return excelNode.namespace();
					}
					@Override
					public String clspath() {
							return typeElement.getQualifiedName().toString();
					}
					@Override
					public AuditMetaData[] auditMetaDatas() {
							if (fields != null) {
									return fields.toArray(new AuditMetaData[fields.size()]);
							}
							return null;
					}
					@Override
					public String validator() {
							return validators.get(namespace());
					}
	  	};
  }

  /*
   * analysis for model classes and get field elements
   * @param typeElement
   */
  public void addValidatedElement(TypeElement typeElement) {
		  // get validated from metedata
	  	Validated validated = typeElement.getAnnotation(Validated.class);
	  	validators.put(validated.namespace(), typeElement.getQualifiedName().toString());
  }
  
  protected FieldNode createFieldNode(final VariableElement field) {
		  // get field name from metedata
	  	final FieldNode fieldNode = field.getAnnotation(FieldNode.class);
			FieldNode result = 	new FieldNode() {
					@Override
					public Class<? extends Annotation> annotationType() {
							return null;
					}
					@Override
					public int index() {
							if (fieldNode != null) {
									int index = fieldNode.index();
									banner = index != -2 ? index - 1 : banner != -1 ? ++ banner : 0;
									return banner;
							}	
							return ++ banner;
					}
					@Override
					public String columnName() {
							if (fieldNode != null) {
									String columnName = fieldNode.columnName();
									if (columnName.length() != 0) {
											return columnName;
									}
							}
							return field.getSimpleName().toString();
					}
	  	};
			return result;
		}
  
  
  private BaseNode createBaseNode(int ctype, VariableElement field) {
		  // get field name from metedata
	  	if (ctype == BasicClass.DOUBLE
	  			|| ctype == BasicClass.FLOAT
	  			|| ctype == BasicClass.BIGDECIMAL) {
	  			final DecimalNode decimalNode = field.getAnnotation(DecimalNode.class);
	  			DecimalNode result = new DecimalNode() {
							@Override
							public Class<? extends Annotation> annotationType() {
									return null;
							}
							@Override
							public int decimal() {
									return decimalNode != null ? decimalNode.decimal() : 2;
							}
	  			};
	  			return createBaseNode(ctype,result);
	  	} else if (ctype == BasicClass.DATE) {
		  		final DateNode dateNode = field.getAnnotation(DateNode.class);
		  		DateNode result = new DateNode() {
							@Override
							public Class<? extends Annotation> annotationType() {
									return null;
							}
							@Override
							public String pattern() {
									return dateNode != null ? dateNode.pattern() : "yyyy-MM-dd HH:mm:ss";
							}
		  		};
		  		return createBaseNode(ctype,result);
	  	} else {
	  			final NoneNode noneNode = field.getAnnotation(NoneNode.class);
	  			return createBaseNode(ctype,noneNode);
	  	}
  }

	private BaseNode createBaseNode(final int ctype,final Annotation annotation) {
			return new BaseNode() {
					@Override
					public Class<? extends Annotation> annotationType() {
							return null;
					}
					@Override
					public int type() {
							return ctype;
					}
					@Override
					public DecimalNode decimal() {
							if (annotation != null) {
									if (annotation instanceof DecimalNode) {
											return (DecimalNode) annotation;
									}
							}
							return null;
					}
					@Override
					public DateNode dateNode() {
							if (annotation != null) {
									if (annotation instanceof DateNode) {
											return (DateNode) annotation;
									}
							}
							return null;
					}
			};
	}

		private AuditMetaData createAuditMetaData(final VariableElement field, final FieldNode fieldNode, final BaseNode baseNode) {
				AuditMetaData result = new AuditMetaData() {
						@Override
						public Class<? extends Annotation> annotationType() {
								return null;
						}
						@Override
						public String name() {
								return field.getSimpleName().toString();
						}
						@Override
						public int index() {
								return fieldNode.index();
						}
						@Override
						public BaseNode base() {
								return baseNode;
						}
						@Override
						public String column() {
							return fieldNode.columnName();
						}
				};
				return result;
		}
		
		private InnerSummary createExcelSummary() {
				return new InnerSummary() {
						@Override
						public Class<? extends Annotation> annotationType() {
								return null;
						}
						@Override
						public InnerModel[] excelModels() {
								return process.values().toArray(new InnerModel[process.size()]);
						}
				};
		}
		
		protected void makeInstall() {
				if (process.size() != 0) {
						InnerSummary summary = createExcelSummary();
//						System.out.println(this);
						CoreSupport core = new CoreSupport();
						core.make(summary);
						core.install();	
				}
		}

		@Override
		public String toString() {
				InnerSummary summary = createExcelSummary();
				InnerModel[] excelModels = summary.excelModels();
				StringBuilder builder = new StringBuilder("[");
				for (InnerModel excelModel : excelModels) {
						builder.append(" { namespace=" + excelModel.namespace());
						builder.append(", clspath=" + excelModel.clspath());
						builder.append(", validator=" + excelModel.validator());
						AuditMetaData[] metaDatas = excelModel.auditMetaDatas();
						builder.append(", AuditMetaData={");
						for (AuditMetaData metadata : metaDatas) {
								builder.append("[");
								int index = metadata.index();
								String name = metadata.name();
								builder.append("index=" + index);
								builder.append(", name=" + name);
								builder.append(", ");
								BaseNode base = metadata.base();
								builder.append("base={");
								int type = base.type();
								DateNode dateNode = base.dateNode();
								DecimalNode decimal = base.decimal();
								builder.append(" type=" + type);
								if (dateNode != null)
										builder.append(", dateNode=" + dateNode.pattern());
								if (decimal != null)
										builder.append(", decimal=" + decimal.decimal());
								builder.append("}], ");
						}
						builder.append("]}");
				}
				builder.append("}");
				return builder.toString();
		}
		
}
